/*
 * Copyright 2004-2006, Jérôme DUVAL. All rights reserved.
 * Copyright 2010, Karsten Heimrich. All rights reserved.
 * Copyright 2013, Rene Gollent, rene@gollent.com.
 * Distributed under the terms of the MIT License.
 */


#include "ExpanderWindow.h"

#include <algorithm>

#include <Alert.h>
#include <Box.h>
#include <Button.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <ControlLook.h>
#include <Entry.h>
#include <File.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <Menu.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <Path.h>
#include <Screen.h>
#include <ScrollView.h>
#include <StringView.h>
#include <TextView.h>

#include "ExpanderApp.h"
#include "ExpanderThread.h"
#include "ExpanderPreferences.h"
#include "PasswordAlert.h"


const uint32 MSG_SOURCE			= 'mSOU';
const uint32 MSG_DEST			= 'mDES';
const uint32 MSG_EXPAND			= 'mEXP';
const uint32 MSG_SHOW			= 'mSHO';
const uint32 MSG_STOP			= 'mSTO';
const uint32 MSG_PREFERENCES	= 'mPRE';
const uint32 MSG_SOURCETEXT		= 'mSTX';
const uint32 MSG_DESTTEXT		= 'mDTX';
const uint32 MSG_SHOWCONTENTS	= 'mSCT';


class StatusView : public BStringView {
public:
	StatusView()
		:
		BStringView(NULL, "")
	{
	}

	virtual ~StatusView()
	{
	}

	void SetStatus(const BString &text)
	{
		fStatus = text;
		Invalidate();
	}

	void Draw(BRect updateRect)
	{
		BString truncated = fStatus;
		if(fStatus.IsEmpty() == false) {
			be_plain_font->TruncateString(&truncated, B_TRUNCATE_END,
				Bounds().Width());
		}

		SetText(truncated);
		BStringView::Draw(updateRect);
	}

private:
	BString fStatus;
};


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ExpanderWindow"


ExpanderWindow::ExpanderWindow(BRect frame, const entry_ref* ref,
	BMessage* settings)
	:
	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Expander"), B_TITLED_WINDOW,
		B_NORMAL_WINDOW_FEEL),
	fSourcePanel(NULL),
	fDestPanel(NULL),
	fSourceChanged(true),
	fListingThread(NULL),
	fListingStarted(false),
	fExpandingThread(NULL),
	fExpandingStarted(false),
	fSettings(*settings),
	fPreferences(NULL)
{
	_CreateMenuBar();

	fDestButton = new BButton(B_TRANSLATE("Destination"),
		new BMessage(MSG_DEST));
	fSourceButton = new BButton(B_TRANSLATE("Source"),
		new BMessage(MSG_SOURCE));
	fExpandButton = new BButton(B_TRANSLATE("Expand"),
		new BMessage(MSG_EXPAND));

	BSize size = fDestButton->PreferredSize();
	size.width = std::max(size.width, fSourceButton->PreferredSize().width);
	size.width = std::max(size.width, fExpandButton->PreferredSize().width);

	fDestButton->SetExplicitSize(size);
	fSourceButton->SetExplicitSize(size);
	fExpandButton->SetExplicitSize(size);

	fListingText = new BTextView("listingText");
	fListingText->SetText("");
	fListingText->MakeEditable(false);
	fListingText->SetStylable(false);
	fListingText->SetWordWrap(false);
	BFont font = be_fixed_font;
	fListingText->SetFontAndColor(&font);
	fScrollView = new BScrollView("", fListingText,	B_INVALIDATE_AFTER_LAYOUT,
		true, true);

	const float spacing = be_control_look->DefaultItemSpacing();
	BGroupLayout* pathLayout;
	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
		.Add(fBar)
		.AddGroup(B_VERTICAL, B_USE_ITEM_SPACING)
			.AddGroup(B_HORIZONTAL, B_USE_ITEM_SPACING)
				.Add(fSourceButton)
				.Add(fSourceText = new BTextControl(NULL, NULL,
					new BMessage(MSG_SOURCETEXT)))
				.End()
			.AddGroup(B_HORIZONTAL, B_USE_ITEM_SPACING)
				.Add(fDestButton)
				.Add(fDestText = new BTextControl(NULL, NULL,
					new BMessage(MSG_DESTTEXT)))
				.End()
			.AddGroup(B_HORIZONTAL, B_USE_ITEM_SPACING)
				.Add(fExpandButton)
				.AddGroup(B_HORIZONTAL, B_USE_ITEM_SPACING)
					.GetLayout(&pathLayout)
					.Add(fShowContents = new BCheckBox(
						B_TRANSLATE("Show contents"),
						new BMessage(MSG_SHOWCONTENTS)))
					.Add(fStatusView = new StatusView())
					.End()
				.End()
			.SetInsets(B_USE_WINDOW_SPACING)
			.End()
		.Add(fScrollView);

	pathLayout->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
	size = GetLayout()->View()->PreferredSize();
	fSizeLimit = size.Height() - fScrollView->PreferredSize().height - spacing;

	fStatusView->SetExplicitMinSize(BSize(50.0f, B_SIZE_UNSET));
	fStatusView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

	ResizeTo(Bounds().Width(), fSizeLimit);
	SetSizeLimits(size.Width(), 32767.0f, fSizeLimit, fSizeLimit);
	SetZoomLimits(Bounds().Width(), fSizeLimit);
	fPreviousHeight = -1;

	fScrollView->Hide();

	Show();
}


ExpanderWindow::~ExpanderWindow()
{
	if (fDestPanel && fDestPanel->RefFilter())
		delete fDestPanel->RefFilter();

	if (fSourcePanel && fSourcePanel->RefFilter())
		delete fSourcePanel->RefFilter();

	delete fDestPanel;
	delete fSourcePanel;
}


bool
ExpanderWindow::ValidateDest()
{
	BEntry entry(fDestText->Text(), true);
	BVolume volume;
	if (!entry.Exists()) {
		BAlert* alert = new BAlert("destAlert",
			B_TRANSLATE("Destination folder doesn't exist. "
				"Would you like to create it?"),
			B_TRANSLATE("Cancel"), B_TRANSLATE("Create"), NULL,
			B_WIDTH_AS_USUAL, B_INFO_ALERT);
		alert->SetShortcut(0, B_ESCAPE);

		if (alert->Go() == 0)
			return false;

		if (create_directory(fDestText->Text(), 0755) != B_OK) {
			BAlert* alert = new BAlert("destAlert",
				B_TRANSLATE("Failed to create the destination folder."),
				B_TRANSLATE("OK"), NULL, NULL,
				B_WIDTH_AS_USUAL, B_WARNING_ALERT);
			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
			alert->Go();
			return false;
		}

		BEntry newEntry(fDestText->Text(), true);
		newEntry.GetRef(&fDestRef);
		return true;
	}

	if (!entry.IsDirectory()) {
		BAlert* alert = new BAlert("destAlert",
			B_TRANSLATE("The destination is not a folder."),
			B_TRANSLATE("OK"), NULL, NULL,
			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
		alert->Go();
		return false;
	}

	if (entry.GetVolume(&volume) != B_OK || volume.IsReadOnly()) {
		BAlert* alert = new BAlert("destAlert",
			B_TRANSLATE("The destination is read only."),
			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
			B_WARNING_ALERT);
		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
		alert->Go();
		return false;
	}

	entry.GetRef(&fDestRef);
	return true;
}


void
ExpanderWindow::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MSG_SOURCE:
		{
			BEntry entry(fSourceText->Text(), true);
			entry_ref srcRef;
			if (entry.Exists() && entry.IsDirectory())
				entry.GetRef(&srcRef);
			if (!fSourcePanel) {
				BMessenger messenger(this);
				fSourcePanel = new BFilePanel(B_OPEN_PANEL, &messenger, &srcRef,
					B_FILE_NODE, false, NULL, new RuleRefFilter(fRules), true);
				(fSourcePanel->Window())->SetTitle(
					B_TRANSLATE("Expander: Open"));
			} else
				fSourcePanel->SetPanelDirectory(&srcRef);
			fSourcePanel->Show();
			break;
		}

		case MSG_DEST:
		{
			BEntry entry(fDestText->Text(), true);
			entry_ref destRef;
			if (entry.Exists() && entry.IsDirectory())
				entry.GetRef(&destRef);
			if (!fDestPanel) {
				BMessenger messenger(this);
				fDestPanel = new DirectoryFilePanel(B_OPEN_PANEL, &messenger,
					&destRef, B_DIRECTORY_NODE, false, NULL,
					new DirectoryRefFilter(), true);
			} else
				fDestPanel->SetPanelDirectory(&destRef);
			fDestPanel->Show();
			break;
		}

		case MSG_DIRECTORY:
		{
			entry_ref ref;
			fDestPanel->GetPanelDirectory(&ref);
			fDestRef = ref;
			BEntry entry(&ref);
			BPath path(&entry);
			fDestText->SetText(path.Path());
			fDestPanel->Hide();
			break;
		}

		case B_SIMPLE_DATA:
		case B_REFS_RECEIVED:
			RefsReceived(message);
			break;

		case MSG_EXPAND:
			if (!ValidateDest())
				break;
			if (!fExpandingStarted) {
				StartExpanding();
				break;
			}
			// supposed to fall through
		case MSG_STOP:
			if (fExpandingStarted) {
				fExpandingThread->SuspendExternalExpander();
				BAlert* alert = new BAlert("stopAlert",
					B_TRANSLATE("Are you sure you want to stop expanding this "
						"archive? The expanded items may not be complete."),
					B_TRANSLATE("Stop"), B_TRANSLATE("Continue"), NULL,
					B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT);
				alert->SetShortcut(0, B_ESCAPE);
				if (alert->Go() == 0) {
					fExpandingThread->ResumeExternalExpander();
					StopExpanding();
				} else
					fExpandingThread->ResumeExternalExpander();
			}
			break;

		case MSG_SHOW:
			fShowContents->SetValue(fShowContents->Value() == B_CONTROL_OFF
				? B_CONTROL_ON : B_CONTROL_OFF);
			// supposed to fall through
		case MSG_SHOWCONTENTS:
			// change menu item label
			fShowItem->SetLabel(fShowContents->Value() == B_CONTROL_OFF
				? B_TRANSLATE("Show contents") : B_TRANSLATE("Hide contents"));

			if (fShowContents->Value() == B_CONTROL_OFF) {
				if (fListingStarted)
					StopListing();

				fScrollView->Hide();
				_UpdateWindowSize(false);
			} else {
				fScrollView->Show();
				StartListing();
			}
			break;

		case MSG_SOURCETEXT:
		{
			BEntry entry(fSourceText->Text(), true);
			if (!entry.Exists()) {
				BAlert* alert = new BAlert("srcAlert",
					B_TRANSLATE("The file doesn't exist"),
					B_TRANSLATE("Cancel"), NULL, NULL,
					B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT);
				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
				alert->Go();
				break;
			}

			entry_ref ref;
			entry.GetRef(&ref);
			ExpanderRule* rule = fRules.MatchingRule(&ref);
			if (rule) {
				fSourceChanged = true;
				fSourceRef = ref;
				fShowContents->SetEnabled(true);
				fExpandButton->SetEnabled(true);
				fExpandItem->SetEnabled(true);
				fShowItem->SetEnabled(true);
				break;
			}

			BString string = "The file : ";
			string += fSourceText->Text();
			string += B_TRANSLATE_MARK(" is not supported");
			BAlert* alert = new BAlert("srcAlert", string.String(),
				B_TRANSLATE("Cancel"),
				NULL, NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_INFO_ALERT);
			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
			alert->Go();

			fShowContents->SetEnabled(false);
			fExpandButton->SetEnabled(false);
			fExpandItem->SetEnabled(false);
			fShowItem->SetEnabled(false);
			break;
		}

		case MSG_DESTTEXT:
			ValidateDest();
			break;

		case MSG_PREFERENCES:
			if (fPreferences == NULL)
				fPreferences = new ExpanderPreferences(&fSettings);

			fPreferences->Show();
			break;

		case 'outp':
			if (!fExpandingStarted && fListingStarted) {
				// Check if the vertical scroll bar is at the end
				float max, pos;
				fScrollView->ScrollBar(B_VERTICAL)->GetRange(NULL, &max);
				pos = fScrollView->ScrollBar(B_VERTICAL)->Value();
				bool atEnd = (pos == max);

				BString string;
				int32 i = 0;
				while (message->FindString("output", i++, &string) == B_OK) {
					float length = fListingText->StringWidth(string.String());

					if (length > fLongestLine)
						fLongestLine = length;

					fListingText->Insert(string.String());
				}

				if (atEnd && fScrollView->ScrollBar(B_VERTICAL)->Value() == pos) {
					fScrollView->ScrollBar(B_VERTICAL)->GetRange(NULL, &max);
					fScrollView->ScrollBar(B_VERTICAL)->SetValue(max);
				}
			} else if (fExpandingStarted) {
				BString string;
				int32 i = 0;
				while (message->FindString("output", i++, &string) == B_OK) {
					if (strstr(string.String(), "Enter password") != NULL) {
						fExpandingThread->SuspendExternalExpander();
						BString password;
						PasswordAlert* alert =
							new PasswordAlert("passwordAlert", string);
						alert->Go(password);
						fExpandingThread->ResumeExternalExpander();
						fExpandingThread->PushInput(password);
					}
				}
			}
			break;

		case 'errp':
		{
			BString string;
			if (message->FindString("error", &string) == B_OK
				&& fExpandingStarted) {
				fExpandingThread->SuspendExternalExpander();
				if (strstr(string.String(), "password") != NULL) {
					BString password;
					PasswordAlert* alert = new PasswordAlert("passwordAlert",
						string);
					alert->Go(password);
					fExpandingThread->ResumeExternalExpander();
					fExpandingThread->PushInput(password);
				} else {
					BAlert* alert = new BAlert("stopAlert", string,
						B_TRANSLATE("Stop"), B_TRANSLATE("Continue"), NULL,
						B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT);
					alert->SetShortcut(0, B_ESCAPE);
					if (alert->Go() == 0) {
						fExpandingThread->ResumeExternalExpander();
						StopExpanding();
					} else
						fExpandingThread->ResumeExternalExpander();
				}
			}
			break;
		}

		case 'exit':
			// thread has finished
			// (finished, quit, killed, we don't know)
			// reset window state
			if (fExpandingStarted) {
				fStatusView->SetStatus(B_TRANSLATE("File expanded"));
				StopExpanding();
				OpenDestFolder();
				CloseWindowOrKeepOpen();
			} else if (fListingStarted) {
				fSourceChanged = false;
				StopListing();
				_ExpandListingText();
			} else
				fStatusView->SetStatus("");
			break;

		case 'exrr':
			// thread has finished
			// reset window state

			fStatusView->SetStatus(B_TRANSLATE("Error when expanding archive"));
			CloseWindowOrKeepOpen();
			break;

		default:
			BWindow::MessageReceived(message);
	}
}


bool
ExpanderWindow::CanQuit()
{
	if ((fSourcePanel && fSourcePanel->IsShowing())
		|| (fDestPanel && fDestPanel->IsShowing())) {
		return false;
	}

	if (fExpandingStarted) {
		fExpandingThread->SuspendExternalExpander();
		BAlert* alert = new BAlert("stopAlert",
			B_TRANSLATE("Are you sure you want to stop expanding this "
				"archive? The expanded items may not be complete."),
			B_TRANSLATE("Stop"), B_TRANSLATE("Continue"), NULL,
			B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT);
			alert->SetShortcut(0, B_ESCAPE);

		if (alert->Go() == 0) {
			fExpandingThread->ResumeExternalExpander();
			StopExpanding();
		} else {
			fExpandingThread->ResumeExternalExpander();
			return false;
		}
	}

	return true;
}


bool
ExpanderWindow::QuitRequested()
{
	if (!CanQuit())
		return false;

	if (fListingStarted)
		StopListing();

	be_app->PostMessage(B_QUIT_REQUESTED);
	fSettings.ReplacePoint("window_position", Frame().LeftTop());
	((ExpanderApp*)(be_app))->UpdateSettingsFrom(&fSettings);

	return true;
}


void
ExpanderWindow::RefsReceived(BMessage* message)
{
	entry_ref ref;
	int32 i = 0;
	int8 destinationFolder = 0x63;
	fSettings.FindInt8("destination_folder", &destinationFolder);

	while (message->FindRef("refs", i++, &ref) == B_OK) {
		BEntry entry(&ref, true);
		BPath path(&entry);
		BNode node(&entry);

		if (node.IsFile()) {
			fSourceChanged = true;
			fSourceRef = ref;
			fSourceText->SetText(path.Path());
			if (destinationFolder == 0x63) {
				BPath parent;
				path.GetParent(&parent);
				fDestText->SetText(parent.Path());
				get_ref_for_path(parent.Path(), &fDestRef);
			} else if (destinationFolder == 0x65) {
				fSettings.FindRef("destination_folder_use", &fDestRef);
				BEntry dEntry(&fDestRef, true);
				BPath dPath(&dEntry);
				fDestText->SetText(dPath.Path());
			}

			BEntry dEntry(&fDestRef, true);
			if (dEntry.Exists()) {
				fExpandButton->SetEnabled(true);
				fExpandItem->SetEnabled(true);
			}

			if (fShowContents->Value() == B_CONTROL_ON) {
				StopListing();
				StartListing();
			} else {
				fShowContents->SetEnabled(true);
				fShowItem->SetEnabled(true);
			}

			bool fromApp;
			if (message->FindBool("fromApp", &fromApp) == B_OK) {
				AutoExpand();
			} else
				AutoListing();
		} else if (node.IsDirectory()) {
			fDestRef = ref;
			fDestText->SetText(path.Path());
		}
	}
}


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ExpanderMenu"

void
ExpanderWindow::_CreateMenuBar()
{
	fBar = new BMenuBar("menu_bar", B_ITEMS_IN_ROW, B_INVALIDATE_AFTER_LAYOUT);
	BMenu* menu = new BMenu(B_TRANSLATE("File"));
	menu->AddItem(fSourceItem
		= new BMenuItem(B_TRANSLATE("Set source" B_UTF8_ELLIPSIS),
			new BMessage(MSG_SOURCE), 'O'));
	menu->AddItem(fDestItem
		= new BMenuItem(B_TRANSLATE("Set destination" B_UTF8_ELLIPSIS),
			new BMessage(MSG_DEST), 'D'));
	menu->AddSeparatorItem();
	menu->AddItem(fExpandItem = new BMenuItem(B_TRANSLATE("Expand"),
		new BMessage(MSG_EXPAND), 'E'));
	fExpandItem->SetEnabled(false);
	menu->AddItem(fShowItem = new BMenuItem(B_TRANSLATE("Show contents"),
		new BMessage(MSG_SHOW), 'L'));
	fShowItem->SetEnabled(false);
	menu->AddSeparatorItem();
	menu->AddItem(fStopItem = new BMenuItem(B_TRANSLATE("Stop"),
		new BMessage(MSG_STOP), 'K'));
	fStopItem->SetEnabled(false);
	menu->AddSeparatorItem();
	menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
		new BMessage(B_QUIT_REQUESTED), 'W'));
	fBar->AddItem(menu);

	menu = new BMenu(B_TRANSLATE("Settings"));
	menu->AddItem(fPreferencesItem
		= new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
			new BMessage(MSG_PREFERENCES), ','));
	fBar->AddItem(menu);
}


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ExpanderWindow"

void
ExpanderWindow::StartExpanding()
{
	ExpanderRule* rule = fRules.MatchingRule(&fSourceRef);
	if (!rule)
		return;

	BEntry destEntry(fDestText->Text(), true);
	if (!destEntry.Exists()) {
		BAlert* alert = new BAlert("destAlert",
		B_TRANSLATE("The folder was either moved, renamed or not supported."),
		B_TRANSLATE("Cancel"), NULL, NULL,
			B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT);
		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
		alert->Go();
		return;
	}

	BMessage message;
	message.AddString("cmd", rule->ExpandCmd());
	message.AddRef("srcRef", &fSourceRef);
	message.AddRef("destRef", &fDestRef);

	fExpandButton->SetLabel(B_TRANSLATE("Stop"));
	fSourceButton->SetEnabled(false);
	fDestButton->SetEnabled(false);
	fShowContents->SetEnabled(false);
	fSourceItem->SetEnabled(false);
	fDestItem->SetEnabled(false);
	fExpandItem->SetEnabled(false);
	fShowItem->SetEnabled(false);
	fStopItem->SetEnabled(true);
	fPreferencesItem->SetEnabled(false);

	BEntry entry(&fSourceRef);
	BPath path(&entry);
	BString text(B_TRANSLATE("Expanding '%s'" B_UTF8_ELLIPSIS));
	text.ReplaceFirst("%s", path.Leaf());
	fStatusView->SetStatus(text.String());

	fExpandingThread = new ExpanderThread(&message, new BMessenger(this));
	fExpandingThread->Start();

	fExpandingStarted = true;
}


void
ExpanderWindow::StopExpanding(void)
{
	if (fExpandingThread) {
		fExpandingThread->InterruptExternalExpander();
		fExpandingThread = NULL;
	}

	fExpandingStarted = false;

	fExpandButton->SetLabel(B_TRANSLATE("Expand"));
	fSourceButton->SetEnabled(true);
	fDestButton->SetEnabled(true);
	fShowContents->SetEnabled(true);
	fSourceItem->SetEnabled(true);
	fDestItem->SetEnabled(true);
	fExpandItem->SetEnabled(true);
	fShowItem->SetEnabled(true);
	fStopItem->SetEnabled(false);
	fPreferencesItem->SetEnabled(true);
}


void
ExpanderWindow::_ExpandListingText()
{
	float delta = fLongestLine - fListingText->Frame().Width();

	if (delta > 0) {
		BScreen screen;
		BRect screenFrame = screen.Frame();

		if (Frame().right + delta > screenFrame.right)
			delta = screenFrame.right - Frame().right - 4.0f;

		ResizeBy(delta, 0.0f);
	}

	float minWidth, maxWidth, minHeight, maxHeight;
	GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);

	if (minWidth < Frame().Width() + delta) {
		// set the Zoom limit as the minimal required size
		SetZoomLimits(Frame().Width() + delta,
			std::min(fSizeLimit + fListingText->TextRect().Height()
				+ fLineHeight + B_H_SCROLL_BAR_HEIGHT + 1.0f,
				maxHeight));
	} else {
		// set the zoom limit based on minimal window size allowed
		SetZoomLimits(minWidth,
			std::min(fSizeLimit + fListingText->TextRect().Height()
				+ fLineHeight + B_H_SCROLL_BAR_HEIGHT + 1.0f,
				maxHeight));
	}
}


void
ExpanderWindow::_UpdateWindowSize(bool showContents)
{
	float minWidth, maxWidth, minHeight, maxHeight;
	GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);

	float bottom = fSizeLimit;

	if (showContents) {
		if (fPreviousHeight < 0.0) {
			BFont font;
			font_height fontHeight;
			fListingText->GetFont(&font);
			font.GetHeight(&fontHeight);
			fLineHeight = ceilf(fontHeight.ascent + fontHeight.descent
				+ fontHeight.leading);
			fPreviousHeight = bottom + 10.0 * fLineHeight;
		}
		minHeight = bottom + 5.0 * fLineHeight;
		maxHeight = 32767.0;

		bottom = std::max(fPreviousHeight, minHeight);
	} else {
		minHeight = fSizeLimit;
		maxHeight = fSizeLimit;
		fPreviousHeight = Frame().Height();
	}

	SetSizeLimits(minWidth, maxWidth, minHeight, maxHeight);
	ResizeTo(Frame().Width(), bottom);
}


void
ExpanderWindow::StartListing()
{
	_UpdateWindowSize(true);

	if (!fSourceChanged)
		return;

	fPreviousHeight = -1.0;

	fLongestLine = 0.0f;

	ExpanderRule* rule = fRules.MatchingRule(&fSourceRef);
	if (!rule)
		return;

	BMessage message;
	message.AddString("cmd", rule->ListingCmd());
	message.AddRef("srcRef", &fSourceRef);

	fShowContents->SetEnabled(true);
	fSourceItem->SetEnabled(false);
	fDestItem->SetEnabled(false);
	fExpandItem->SetEnabled(false);
	fShowItem->SetEnabled(true);
	fShowItem->SetLabel(B_TRANSLATE("Hide contents"));
	fStopItem->SetEnabled(false);
	fPreferencesItem->SetEnabled(false);

	fSourceButton->SetEnabled(false);
	fDestButton->SetEnabled(false);
	fExpandButton->SetEnabled(false);

	BEntry entry(&fSourceRef);
	BPath path(&entry);
	BString text(B_TRANSLATE("Creating listing for '%s'" B_UTF8_ELLIPSIS));
	text.ReplaceFirst("%s", path.Leaf());
	fStatusView->SetStatus(text.String());
	fListingText->SetText("");
	fListingText->MakeSelectable(false);

	fListingThread = new ExpanderThread(&message, new BMessenger(this));
	fListingThread->Start();

	fListingStarted = true;
}


void
ExpanderWindow::StopListing(void)
{
	if (fListingThread) {
		fListingThread->InterruptExternalExpander();
		fListingThread = NULL;
	}

	fListingStarted = false;

	fListingText->MakeSelectable(true);
	fShowContents->SetEnabled(true);
	fSourceItem->SetEnabled(true);
	fDestItem->SetEnabled(true);
	fExpandItem->SetEnabled(true);
	fShowItem->SetEnabled(true);
	fStopItem->SetEnabled(false);
	fPreferencesItem->SetEnabled(true);

	fSourceButton->SetEnabled(true);
	fDestButton->SetEnabled(true);
	fExpandButton->SetEnabled(true);
	fStatusView->SetStatus("");
}


void
ExpanderWindow::CloseWindowOrKeepOpen()
{
	bool expandFiles = false;
	fSettings.FindBool("automatically_expand_files", &expandFiles);

	bool closeWhenDone = false;
	fSettings.FindBool("close_when_done", &closeWhenDone);

	if (expandFiles || closeWhenDone)
		PostMessage(B_QUIT_REQUESTED);
}


void
ExpanderWindow::OpenDestFolder()
{
	bool openFolder = true;
	fSettings.FindBool("open_destination_folder", &openFolder);

	if (!openFolder)
		return;

	BMessage* message = new BMessage(B_REFS_RECEIVED);
	message->AddRef("refs", &fDestRef);
	BPath path(&fDestRef);
	BMessenger tracker("application/x-vnd.Be-TRAK");
	tracker.SendMessage(message);
}


void
ExpanderWindow::AutoListing()
{
	bool showContents = false;
	fSettings.FindBool("show_contents_listing", &showContents);

	if (!showContents)
		return;

	fShowContents->SetValue(B_CONTROL_ON);
	fShowContents->Invoke();
}


void
ExpanderWindow::AutoExpand()
{
	bool expandFiles = false;
	fSettings.FindBool("automatically_expand_files", &expandFiles);

	if (!expandFiles) {
		AutoListing();
		return;
	}

	fExpandButton->Invoke();
}
